//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>
#include <buola/io/curi.h>
#include <buola/io/curischeme.h>
#include <buola/os/cenv.h>
#include <buola/utility/random.h>
#include <buola/algorithm/trim.h>
#include <buola/algorithm/split.h>

#include <unistd.h>

namespace buola { namespace io {

// characters that are always escaped when setting components
static const uint8_t sEscapedChars[256]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
    1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, // 2    ! ' ( ) * - .
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // 3    0-9
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4    A-O
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 5    P-Z _
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6    a-o
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 7    p-z ~
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // F
};
    
// characters that are always escaped when escaping the host
static const uint8_t sEscapedCharsHost[256]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
    1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, // 2    ! ' ( ) * - .
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // 3    0-9
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4    A-O
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, // 5    P-Z [ ] _
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6    a-o
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 7    p-z ~
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // F
};

// characters that are always escaped when escaping the path
static const uint8_t sEscapedCharsPath[256]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
    1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, // 2    ! ' ( ) * - . /
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // 3    0-9
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4    A-O
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 5    P-Z _
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6    a-o
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 7    p-z ~
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // F
};

// characters that are always escaped when escaping the query
static const uint8_t sEscapedCharsQuery[256]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
    1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 2    ! & ' ( ) * + - . /
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, // 3    0-9 ;
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4    A-O
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 5    P-Z _
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6    a-o
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 7    p-z ~
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // F
};

// characters that are not unescaped for comparison
static const uint8_t sReserved[256]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, // 2    # $ & + , /
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, // 3    : ; = ?
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4    @
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, // 5    [ ]
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
};

// character to hexadecimal value
// must use value%0x1f
static const uint8_t sHexValue[32]=
{
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 1
};

//hexadecimal digits
static const uint8_t sHexDigit[16]=
{
    '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};

static std::string uri_escape_with_table(const std::string &pSrc,const uint8_t *pTable)
{
    std::string lDst;
    const uint8_t *lSrc=reinterpret_cast<const uint8_t*>(pSrc.data());
    int lExtra=10;
    lDst.resize(pSrc.length()+20);
    int d=0;
    for(int s=0;s<pSrc.size();s++)
    {
        if(pTable[lSrc[s]]) //must escape
        {
            if(--lExtra<0)
            {
                lDst.resize(lDst.length()+20);
                lExtra+=10;
            }
            lDst[d++]='%';
            lDst[d++]=sHexDigit[lSrc[s]>>4];
            lDst[d++]=sHexDigit[lSrc[s]&0xf];
        }
        else
        {
            lDst[d++]=lSrc[s];
        }
    }
    
    lDst.resize(d);
    return lDst;
}

std::string uri_escape(const std::string &pSrc)
{
    return uri_escape_with_table(pSrc,sEscapedChars);
}

std::string CURI::ToString() const
{
    //empty URIs don't have a scheme
    if(mFlags&FLAG_EMPTY) return std::string();
    
    if(!mScheme)
    {
        if(mFlags&FLAG_SIMPLEPATH) //must escape
            return uri_escape_with_table(mText,sEscapedCharsPath);
        else
            return mText;
    }
    else
    {
        if(mFlags&FLAG_SIMPLEPATH)
        {
            if(mText.empty()||mText[0]!='/') //relative path, no //
                return mScheme->GetName()+':'+uri_escape_with_table(mText,sEscapedCharsPath);
            else
                return mScheme->GetName()+"://"+uri_escape_with_table(mText,sEscapedCharsPath);
        }
        else
        {
            return mScheme->GetName()+':'+mText;
        }
    }
}

std::wstring CURI::ToWString() const
{
    return utf32(ToString());
}

CURI::TRange CURI::UserRange() const
{
    // it will be right after the double slash (which must be present)
    if(mFlags&FLAG_HASUSER)
        return TRange(2,mText.find_first_of("@:",2));
    else if(mFlags&FLAG_HASAUTHORITY)
        return TRange(2,2);
    else
        return TRange(0,0);
}

CURI::TRange CURI::PassRange() const
{
    if(mFlags&FLAG_HASPASS)
    {
        //if we are here, there is a ':' and a '@', so just look for them after the double slash
        std::size_t lStart=mText.find(':',2)+1;
        return TRange(lStart,mText.find('@',lStart));
    }
    else //it should go after the username, wherever that is
    {
        std::size_t lUserEnd=UserRange().second;
        return TRange(lUserEnd,lUserEnd);
    }
}

CURI::TRange CURI::HostRange() const
{
    //if there is an authority, the host might be empty, but it's there
    if(mFlags&FLAG_HASAUTHORITY)
    {
        std::size_t lStart=2;
        
        if(mFlags&FLAG_HASUSER)
            lStart=mText.find('@',lStart)+1;
        
        std::size_t lEnd=mText.find_first_of("/:?#",lStart);
        if(lEnd==std::string::npos)
            return TRange(lStart,mText.size());
        return TRange(lStart,lEnd);
    }

    return TRange(0,0);
}

CURI::TRange CURI::PortRange() const
{
    if(mFlags&FLAG_HASPORT)
    {
        std::size_t lStart=HostRange().second+1;
        std::size_t lEnd=mText.find_first_of("/?#",lStart);
        if(lEnd==std::string::npos)
            return TRange(lStart,mText.size());
        return TRange(lStart,lEnd);
    }
    else //it should go after the host, wherever that is
    {
        std::size_t lHostEnd=HostRange().second;
        return TRange(lHostEnd,lHostEnd);
    }
}

CURI::TRange CURI::PathRange() const
{
    if(mFlags&FLAG_SIMPLEPATH)
    {
        return TRange(0,mText.length());
    }
    
    if(mFlags&FLAG_HASAUTHORITY)
    {
        std::size_t lStart=mText.find_first_of("/?#",2);
        if(lStart==std::string::npos||mText[lStart]=='?'||mText[lStart]=='#') //everything is authority
            return TRange(mText.length(),mText.length());
        std::size_t lEnd=mText.find_first_of("?#",lStart+1);
        if(lEnd==std::string::npos)
            return TRange(lStart,mText.length());
        return TRange(lStart,lEnd);
    }
    else
    {
        std::size_t lEnd=mText.find_first_of("?#");
        if(lEnd==std::string::npos)
            return TRange(0,mText.length());
        return TRange(0,lEnd);
    }
}

CURI::TRange CURI::FolderRange() const
{
    TRange lRange=PathRange();

    if(lRange.first==lRange.second) return lRange;

    std::size_t lSlash=mText.rfind('/',lRange.second-1);

    if(lSlash==std::string::npos||lSlash<lRange.first) return TRange(lRange.first,lRange.first);

    if(lSlash==lRange.first)
        return TRange(lRange.first,lRange.first+1);

    return TRange(lRange.first,lSlash);
}

CURI::TRange CURI::NameRange() const
{
    TRange lRange=PathRange();

    if(lRange.first==lRange.second) return lRange;

    std::size_t lSlash=mText.rfind('/',lRange.second-1);

    if(lSlash==std::string::npos||lSlash<lRange.first) return lRange;

    return TRange(lSlash+1,lRange.second);
}

CURI::TRange CURI::ExtensionRange() const
{
    TRange lRange=PathRange();

    if(lRange.first==lRange.second) return lRange;

    std::size_t lSlashDot=mText.find_last_of("./",lRange.second-1);

    if(lSlashDot==std::string::npos||lSlashDot<=lRange.first||mText[lSlashDot]=='/'||mText[lSlashDot-1]=='/')
        return TRange(lRange.second,lRange.second);

    return TRange(lSlashDot+1,lRange.second);
}

CURI::TRange CURI::NameWithoutExtensionRange() const
{
    TRange lRange=PathRange();

    if(lRange.first==lRange.second) return lRange;

    std::size_t lSlashDot=mText.find_last_of("./",lRange.second-1);

    if(lSlashDot==std::string::npos||lSlashDot<lRange.first)
        return lRange;
    if(mText[lSlashDot]=='/')
        return TRange(lSlashDot+1,lRange.second);
    if(lSlashDot==lRange.first||mText[lSlashDot-1]=='/')
        return TRange(lSlashDot,lRange.second);

    std::size_t lSlash=mText.rfind('/',lSlashDot-1);

    if(lSlash==std::string::npos) return TRange(lRange.first,lSlashDot);

    return TRange(lSlash+1,lSlashDot);
}

CURI::TRange CURI::QueryRange() const
{
    if(mFlags&FLAG_HASQUERY)
    {
        std::size_t lStart=mText.find('?')+1;
        if(mFlags&FLAG_HASFRAGMENT)
        {
            return TRange(lStart,mText.find('#',lStart));
        }
        return TRange(lStart,mText.size());
    }
    else
    {
        if(mFlags&FLAG_HASFRAGMENT)
        {
            std::size_t lHash=mText.rfind('#');
            return TRange(lHash,lHash);
        }
        return TRange(mText.size(),mText.size());
    }
}

CURI::TRange CURI::FragmentRange() const
{
    if(mFlags&FLAG_HASFRAGMENT)
    {
        return TRange(mText.find('#')+1,mText.size());
    }
    return TRange(mText.size(),mText.size());
}

std::string CURI::UnescapeRange(const TRange &pRange) const
{
    if(mFlags&FLAG_SIMPLEPATH)
        return mText.substr(pRange.first,pRange.second-pRange.first);

    std::string lDst;
    if(pRange.second>pRange.first)
    {
        lDst.resize(pRange.second-pRange.first);
        for(int d=0,s=pRange.first;s<pRange.second;s++)
        {
            if(mText[s]!='%')
            {
                lDst[d++]=mText[s];
            }
            else
            {
                lDst[d++]=(sHexValue[((uint8_t)mText[s+1])&0x1f]<<4)+sHexValue[((uint8_t)mText[s+2])&0x1f];
                s+=2;
            }
        }
    }

    return lDst;
}

std::string CURI::User() const
{
    if(mFlags&FLAG_HASUSER)
        return UnescapeRange(UserRange());
    return std::string();
}

std::string CURI::Pass() const
{
    if(mFlags&FLAG_HASPASS)
        return UnescapeRange(PassRange());
    return std::string();
}

std::string CURI::Host() const
{
    if(mFlags&FLAG_HASAUTHORITY)
        return UnescapeRange(HostRange());
    return std::string();
}

std::string CURI::Port() const
{
    if(mFlags&FLAG_HASPORT)
        return UnescapeRange(PortRange());
    return std::string();
}

std::string CURI::Folder() const
{
    return UnescapeRange(FolderRange());
}

std::string CURI::Name() const
{
    return UnescapeRange(NameRange());
}

std::string CURI::Extension() const
{
    return UnescapeRange(ExtensionRange());
}

std::string CURI::NameWithoutExtension() const
{
    return UnescapeRange(NameWithoutExtensionRange());
}

bool CURI::RelativePath() const
{
    TRange lRange=PathRange();
    return lRange.first==lRange.second||mText[lRange.first]!='/';
    
}

std::string CURI::Query() const
{
    if(mFlags&FLAG_HASQUERY)
        return UnescapeRange(QueryRange());
    return std::string();
}

std::string CURI::Fragment() const
{
    if(mFlags&FLAG_HASFRAGMENT)
        return UnescapeRange(FragmentRange());
    return std::string();
}

void CURI::GetPathComponents(std::vector<std::string> &pDst) const
{
    pDst.clear();

    std::string lPath=Path();
    
    if(lPath.empty()) return;

    if(lPath[0]=='/')
        pDst.push_back("/");
        
    split(lPath,pDst,fn::equal_to('/'),nDeleteEmpty);
}

std::map<std::string,std::string> CURI::GetQueryItems() const
{
    std::map<std::string,std::string> lItems;

    TRange lRange=QueryRange();

    while(lRange.first<lRange.second)
    {
        auto lEnd=mText.find_first_of("#&;=",lRange.first);
        
        if(lEnd==std::string::npos)
        {
            lItems[UnescapeRange(TRange(lRange.first,mText.size()))]="";
            break;
        }
        else if(mText[lEnd]=='=')
        {
            auto lValueEnd=mText.find_first_of("#&;",lEnd+1);
            if(lValueEnd==std::string::npos)
                lValueEnd=mText.size();
            lItems[UnescapeRange(TRange(lRange.first,lEnd))]=
                    UnescapeRange(TRange(lEnd+1,lValueEnd));
            lRange.first=lValueEnd+1;
        }
        else
        {
            lItems[UnescapeRange(TRange(lRange.first,lEnd))]="";
            lRange.first=lEnd+1;
        }
    }

    return lItems;
}

bool CURI::HasQueryItem(const std::string &pItem) const
{
    std::map<std::string,std::string> lItems=GetQueryItems();

    return lItems.find(pItem)!=lItems.end();
}

std::string CURI::GetQueryItem(const std::string &pItem) const
{
    std::map<std::string,std::string> lItems=GetQueryItems();

    auto lIt=lItems.find(pItem);
    if(lIt!=lItems.end())
    {
        return lIt->second;
    }

    return std::string();
}

void CURI::SetUser(const std::string &pUser)
{
    TRange lRange=UserRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASAUTHORITY))
    {
        lText+="//";
        mFlags|=FLAG_HASAUTHORITY;
    }

    lText+=uri_escape_with_table(pUser,sEscapedChars);

    if(!(mFlags&FLAG_HASUSER))
    {
        lText+='@';
        mFlags|=FLAG_HASUSER;
    }
    
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetPass(const std::string &pPass)
{
    TRange lRange=PassRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASAUTHORITY))
    {
        lText+="//";
        mFlags|=FLAG_HASAUTHORITY;
    }

    if(!(mFlags&FLAG_HASPASS))
    {
        lText+=':';
        mFlags|=FLAG_HASPASS;
    }

    lText+=uri_escape_with_table(pPass,sEscapedChars);

    if(!(mFlags&FLAG_HASUSER))
    {
        lText+='@';
        mFlags|=FLAG_HASUSER;
    }

    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetHost(const std::string &pHost)
{
    TRange lRange=HostRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASAUTHORITY))
    {
        lText+="//";
        mFlags|=FLAG_HASAUTHORITY;
    }

    lText+=uri_escape_with_table(pHost,sEscapedCharsHost);
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetPort(const std::string &pPort)
{
    TRange lRange=PortRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASAUTHORITY))
    {
        lText+="//";
        mFlags|=FLAG_HASAUTHORITY;
    }

    if(!(mFlags&FLAG_HASPORT))
    {
        lText+=':';
        mFlags|=FLAG_HASPORT;
    }

    lText+=uri_escape_with_table(pPort,sEscapedChars);
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetPath(const std::string &pPath)
{
    if(mFlags&FLAG_SIMPLEPATH)
    {
        mText=pPath;
        return;
    }
    
    TRange lRange=PathRange();

    std::string lText=mText.substr(0,lRange.first);
    if((lRange.first==lRange.second||mText[lRange.first]!='/') && pPath[0]!='/')
        lText+='/';
    lText+=uri_escape_with_table(pPath,sEscapedCharsPath);
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetFolder(const std::string &pFolder)
{
    TRange lRange=FolderRange();

    std::string lText=mText.substr(0,lRange.first);

    if(mFlags&FLAG_SIMPLEPATH)
        lText+=pFolder;
    else
        lText+=uri_escape_with_table(pFolder,sEscapedCharsPath);

    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetName(const std::string &pName)
{
    TRange lRange=NameRange();

    std::string lText=mText.substr(0,lRange.first);

    if(mFlags&FLAG_SIMPLEPATH)
        lText+=pName;
    else
        lText+=uri_escape_with_table(pName,sEscapedCharsPath);

    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetExtension(const std::string &pExtension)
{
    TRange lRange=ExtensionRange();

    std::string lText=mText.substr(0,lRange.first);

    if(mFlags&FLAG_SIMPLEPATH)
        lText+=pExtension;
    else
        lText+=uri_escape_with_table(pExtension,sEscapedCharsPath);

    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetNameWithoutExtension(const std::string &pName)
{
    TRange lRange=NameWithoutExtensionRange();

    std::string lText=mText.substr(0,lRange.first);

    if(mFlags&FLAG_SIMPLEPATH)
        lText+=pName;
    else
        lText+=uri_escape_with_table(pName,sEscapedCharsPath);

    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetQuery(const std::string &pQuery)
{
    TRange lRange=QueryRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASQUERY))
    {
        lText+='?';
        mFlags|=FLAG_HASQUERY;
    }

    lText+=uri_escape_with_table(pQuery,sEscapedCharsQuery);
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::SetFragment(const std::string &pFragment)
{
    TRange lRange=QueryRange();

    std::string lText=mText.substr(0,lRange.first);

    if(!(mFlags&FLAG_HASFRAGMENT))
    {
        lText+='?';
        mFlags|=FLAG_HASFRAGMENT;
    }

    lText+=uri_escape_with_table(pFragment,sEscapedCharsQuery);
    lText+=mText.substr(lRange.second);
    std::swap(mText,lText);
}

void CURI::AppendPathComponent(const std::string &pPath)
{
    TRange lRange=PathRange();

    if(pPath[0]=='/')
        mText.insert(lRange.second,pPath);
    else
        mText.insert(lRange.second,"/"+pPath);
}

void CURI::AppendOrReplacePath(const std::string &pPath)
{
    if(pPath[0]=='/')
        SetPath(pPath);
    else
        AppendPathComponent(pPath);
}

void CURI::AddQueryItem(const std::string &pItem,const std::string &pValue)
{
    if(mFlags&FLAG_HASQUERY)
    {
        TRange lQueryRange=QueryRange();

        while(lQueryRange.first<lQueryRange.second)
        {
            std::size_t lEnd=mText.find_first_of("#&=",lQueryRange.first);

            if(lEnd==std::string::npos) break;
            if(mText[lEnd]=='=')
            {
                std::size_t lValueEnd=mText.find_first_of("#&",lEnd+1);
                if(lValueEnd==std::string::npos)
                    lValueEnd=mText.size();

                if(UnescapeRange(TRange(lQueryRange.first,lEnd-lQueryRange.first))==pItem)
                {
                    mText.replace(lEnd+1,lValueEnd-(lEnd+1),uri_escape_with_table(pValue,sEscapedCharsQuery));
                    return;
                }
                lQueryRange.first=lValueEnd+1;
            }
            else
            {
                if(UnescapeRange(TRange(lQueryRange.first,lEnd-lQueryRange.first))==pItem)
                {
                    mText.insert(lEnd,"="+uri_escape_with_table(pValue,sEscapedCharsQuery));
                    return;
                }
                lQueryRange.first=lEnd+1;
            }
        }
        //item not found... append
        mText.insert(lQueryRange.second,"&"+uri_escape_with_table(pItem,sEscapedCharsQuery)+"="+
                                            uri_escape_with_table(pValue,sEscapedCharsQuery));
    }
    else
    {
        TRange lQueryRange=QueryRange();

        mText.insert(lQueryRange.second,"?"+uri_escape_with_table(pItem,sEscapedCharsQuery)+"="+
                                            uri_escape_with_table(pValue,sEscapedCharsQuery));
        mFlags|=FLAG_HASQUERY;
    }
}

///////////////////// URI parsing //////////////////////

///////////////////////// URI manipulation //////////////////////

#if 0
void CURI::Correct()
{
    std::string lString;

    if(mPath.length())
    {
        lString.clear();
        lString+=mPath;
        lString+='/';
        lString+=mName;
    }
    else
    {
        lString=mName;
    }

    mName.clear();
    mPath.clear();

    if(!lString.length()) return;

    while(lString.length()>1&&lString[lString.length()-1]=='/')
    {
        lString.resize(lString.length()-1);
    }

    const std::string &lFull=lString;

    int lStart=0;
    int lEnd=0;

    if(lFull[0]=='/')
    {
        mPath+='/';
        lStart++;

        if(!lFull.length()) return;
    }

    bool lDepth=0;

    while(1)
    {
        while(lFull[lStart]=='/') lStart++;

        lEnd=lFull.find('/',lStart);

        if(lEnd<0) break;

        if(lDepth)
            mPath+='/';

        mPath.append(lFull,lStart,lEnd-lStart);

        lDepth++;
        lStart=lEnd+1;
    }

    mName.assign(lFull,lStart,lFull.length()-lStart);
}
#endif

void CURI::Normalize()
{
    if(mScheme)
        mScheme->NormalizeURI(*this);
}

void CURI::Canonicalize()
{
    if(mScheme)
        mScheme->CanonicalizeURI(*this);
}

void CURI::MakeParent()
{
    TRange lRange=PathRange();

    //nothing to remove
    if(lRange.second==lRange.first||(lRange.second==lRange.first+1&&mText[lRange.first]=='/'))
        return;

    std::size_t lLastSlash=mText.rfind('/',lRange.second-1);

    if(lLastSlash!=std::string::npos) //there is some slash
    {
        if(lLastSlash==lRange.first) //it's the initial slash... keep it
            lRange.first=lRange.first+1;
        else if(lLastSlash>lRange.first)
            lRange.first=lLastSlash+1;
    }

    mText.erase(lRange.first,lRange.second-lRange.first);
}

void CURI::MakeAbsolute(const CURI &pBase)
{
    if(IsRelativeURI())
        *this=pBase/Path();
}

CURI uri(const std::string &pURI)
{
    std::string lText=pURI;
    CURI::EFlags lFlags=CURI::FLAG_NONE;
    CURIScheme *lScheme=nullptr;

    trim(lText);
    if(lText.empty()) return io::CURI();

    //find scheme
    std::size_t lPos=0;
    
    std::size_t lSchemeEnd=lText.find_first_of("/?#:@%");
    if(lSchemeEnd!=std::string::npos&&lText[lSchemeEnd]==':')
    {
        lScheme=CURIScheme::GetScheme(lText.substr(0,lSchemeEnd));
        lText=lText.substr(lSchemeEnd+1);
    }
    
    //if two slashes are next, there is an authority
    if(lText.length()>=2&&lText[0]=='/'&&lText[1]=='/')
    {
        lFlags|=CURI::FLAG_HASAUTHORITY;

        bool lFoundPass=false;
        bool lFoundUser=false;
        bool lFoundPort=false;
        
        for(lPos=2;lPos<lText.length();lPos++)
        {
            if(lText[lPos]==':')
            {
                lFoundPort=true;
                if(!lFoundUser)
                    lFoundPass=true;
            }
            else if(lText[lPos]=='@')
            {
                lFoundUser=true;
                lFoundPort=false;
            }
            else if(lText[lPos]=='/'||lText[lPos]=='?'||lText[lPos]=='#')
            {
                break;
            }
        }

        if(lFoundUser)
        {
            if(lFoundPass)
                lFlags|=CURI::FLAG_HASPASS;
            lFlags|=CURI::FLAG_HASUSER;
        }
        
        if(lFoundPort)
            lFlags|=CURI::FLAG_HASPORT;
    }

    bool lFoundQuery=false;
    bool lFoundFragment=false;

    for(;lPos<lText.length();lPos++)
    {
        if(lText[lPos]=='?')
        {
            if(!lFoundFragment)
                lFoundQuery=true;
        }
        else if(lText[lPos]=='#')
        {
            lFoundFragment=true;
        }
    }
    
    if(lFoundQuery)
        lFlags|=CURI::FLAG_HASQUERY;
    if(lFoundFragment)
        lFlags|=CURI::FLAG_HASFRAGMENT;

    return CURI(lScheme,lText,lFlags);
}

CURI arg(int i,const CCmdLine &pCmdLine)
{
    return file(pCmdLine.Arg(i));
}

CURI get_cwd()
{
    char *lWD=getcwd(nullptr,0);
    std::string lString(lWD);
    free(lWD);

    return file(lString);
}

CURI get_temp(const std::string &pPattern)
{
    std::string lPattern=pPattern;

    if(lPattern.empty())
    {
        lPattern="/tmp/buola*";
    }

    int lStar=lPattern.find('*');

    if(lStar<0)
    {
        lStar=lPattern.length();
    }
    else
    {
        lPattern.erase(lStar,1);
    }

    std::string lNewPattern=lPattern;
    std::string lAddPattern;

    for(int i=0;i<6;i++)
    {
        lAddPattern+=random::get<char>('A','Z');
    }

    lNewPattern.insert(lStar,lAddPattern);

    return file(lNewPattern);
}

CURI get_home()
{
    return file(CEnv::Get("HOME","/"));
}

CURI CURI::GetParent() const
{
    TRange lRange=PathRange();

    if(lRange.first!=lRange.second)
    {
        std::size_t lSlash=mText.rfind('/',lRange.second-1);

        if(lSlash==std::string::npos||lSlash<lRange.first)
            lSlash=lRange.first;

        lRange.second=lSlash;

        return CURI(mScheme,mText.substr(0,lRange.second),mFlags);
    }
    else
    {
        return *this;
    }
}

/*namespace io*/ } /*namespace buola*/ }
